package risoe.syslab.fhnew.hw;

/*
 * Copyright (c) 2012-2014, Technical University of Denmark (DTU)
 * All rights reserved.
 * 
 * The Flexhouse 2.0 platform ("this software") is licensed under the
 * BSD 3-clause license.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *   - Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *   - Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *   - Neither the name of DTU nor the names of its contributors may be used to
 *     endorse or promote products derived from this software without specific
 *     prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE TECHNICAL UNIVERSITY OF DENMARK BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import risoe.syslab.fhnew.config.ActuatorConfig;
import risoe.syslab.fhnew.config.BuildingConfig;
import risoe.syslab.fhnew.config.HWInterfaceConfig;
import risoe.syslab.fhnew.config.SensorConfig;
import risoe.syslab.fhnew.core.MonitoredModule;
import risoe.syslab.fhnew.db.LoggerManager;
import risoe.syslab.model.CompositeMeasurement;

/**
 * The HWModule serves as a link between the ProcessImage and all HardwareInterfaces. Also handles
 * startup and shutdown of HardwareInterfaces.
 */
public class HWModule implements MonitoredModule {
	private BuildingConfig building;
	private ProcessImage procimg;
	private LoggerManager loggerManager;
	private HashMap<String,HardwareInterface> interfaces;
	private HashMap<String, HardwareInterface> deviceInterfaceMap;

	/**
	 * Starts up a new HWModule using the configuration supplied in a XML file and a reference to a dormant
	 * LoggerManager
	 * @param cfgfile the path to an XML file describing the building
	 * @param loggerMan a dormant(not yet started) LoggerManager
	 */
	public HWModule(String cfgfile, LoggerManager loggerMan) {
		interfaces = new HashMap<String,HardwareInterface>();
		building = new BuildingConfig();
		building.parseConfig(cfgfile, loggerMan.getPlatformEventLogger());
		deviceInterfaceMap = new HashMap<String, HardwareInterface>();
		procimg = new ProcessImage(this);
		fillProcessImage();

		// Ready LoggerManager and start it
		loggerManager = loggerMan;
		loggerManager.setProcessImage(procimg);
		loggerManager.startLoggers();

		loggerManager.getPlatformEventLogger().logEvent("Core", "NOTICE", 
			" ------------ PLATFORM STARTED ----------- ");

		startHWThreads();
	}

	/**
	 * @return the BuildingConfig associated with this HWModule
	 */
	public BuildingConfig getBuildingConfig() {
		return building;
	}

	/**
	 * @return the ProcessImage associated with this HWModule
	 */
	public ProcessImage getProcessImage() {
		return procimg;
	}

	/**
	 * Starts up all child HardwareInterfaces and maps sensors/actuators to their appropriate
	 * interfaces.
	 */
	private void startHWThreads() {
		ArrayList<HWInterfaceConfig> ifs=building.getHWInterfaces();
		for (HWInterfaceConfig hic:ifs) {
			try {
				HardwareInterface hif=hic.getInterfaceInstance();
				interfaces.put(hif.getName(), hif);
				boolean success=hif.startInterface(this, loggerManager,
						building.getSensorsWithInterface(hif.getName()),
						building.getActuatorsWithInterface(hif.getName()));
			} catch (Exception e) { //TODO: Error handling probably needs rework. startInterface() catches everything right now.
				loggerManager.getPlatformEventLogger().logEvent(hic.getName(), "Initialization", "Error: "+e.getMessage());
			}
		}

		// Map sensors and actuators to their respective interface instances
		ArrayList<SensorConfig> sensors = building.getAllSensors();
		for (SensorConfig sc: sensors){
			deviceInterfaceMap.put(sc.getName(), interfaces.get(sc.getInterface().getName()));
		}
		ArrayList<ActuatorConfig> actuators = building.getAllActuators();
		for (ActuatorConfig ac : actuators){
			deviceInterfaceMap.put(ac.getName(), interfaces.get(ac.getInterface().getName()));
		}
	}

	/**
	 * Ends the execution of all child HardwareInterfaces.
	 */
	public void stopHWThreads() {
		for (HardwareInterface intf : interfaces.values()) {
			intf.stopInterface();
		}
	}

	/**
	 * Fills the ProcessImage using the parsed SensorConfigs and ActuatorConfigs. Calling this method
	 * will lock the ProcessImage.
	 */
	private void fillProcessImage() {
		ArrayList<SensorConfig> srs=building.getAllSensors();
		for (SensorConfig sc:srs) {
			procimg.addSensor(sc);
		}
		ArrayList<ActuatorConfig> acs=building.getAllActuators();
		for (ActuatorConfig ac:acs) {
			procimg.addActuator(ac);
		}
		procimg.lockImage();
	}

	/**
	 * Called by the processImage when an actuator setpoint has been changed
	 * @param actuator the actuator whose setpoint has changed
	 */
	public void handleActuation(Actuator actuator){
		// Lookup the HWInterface that should handle this actuation
		HardwareInterface intf = deviceInterfaceMap.get(actuator.getName());
		if (intf != null){
			intf.applyActuation(actuator);
		}
	}

	/**
	 * Queries the appropriate HardwareInterface for a new measurement
	 * @param sensor the sensor that should be read
	 * @return a CompositeMeasurement of the reading, or null if no such polling is possible
	 */
	public CompositeMeasurement attemptRead(Sensor sensor){
		HardwareInterface intf = deviceInterfaceMap.get(sensor.getName());
		if (intf != null){
			return intf.read(sensor);
		}
		loggerManager.getPlatformEventLogger().logEvent("HWModule", "Error", "No interface matches sensor: "+sensor);
		return null;
	}

	/**
	 * Propagate data to the ProcessImage
	 * @param data a HashMap containing new measurements from a HardwareInterface
	 */
	public void newData(HashMap<String, CompositeMeasurement> data) {
		procimg.newData(data);
	}

  @Override
  public Status getStatus() {
    MonitoredModule.Status mms=MonitoredModule.Status.Running;
    Iterator<String> ifnames=interfaces.keySet().iterator();
    while (ifnames.hasNext()) {
      HardwareInterface hwif=interfaces.get(ifnames.next());
      if (hwif!=null) {
        if (!hwif.isRunning()) {
          mms=MonitoredModule.Status.Fault;
        }
      }
    }
    return mms;
  }

  @Override
  public String getFaultMessage() {
    StringBuffer ermsg=new StringBuffer();
    Iterator<String> ifnames=interfaces.keySet().iterator();
    while (ifnames.hasNext()) {
      HardwareInterface hwif=interfaces.get(ifnames.next());
      if (hwif!=null) {
        if (!hwif.isRunning()) {
          ermsg.append(hwif.getErrorMessage()+", ");
        }
      }
    }
    return ermsg.toString();
  }
}
